#if HACK_HARDCODED_COMPILE_UNIT_ADDED_CPP == 0
#include "iConfig.hpp"
#endif

#include <glf/core/zip_reader.h>
#include <glf/io/fileStream.h>

//#include "vox_filesystem.h"
//#include "MSHeaders.h"
//#include "vox_macro.h"
#include <string.h>

#define ZIP_MAGIC_NUMBER 0x05044c51 //'PK' 03 04 +1 //Glitch obfuscating

namespace glf
{

namespace core
{

CZipReader::CZipReader(DataStream* inFile, const char* filename, bool ignoreCase, bool ignorePaths, bool scanHeader)
: IgnoreCase(ignoreCase)
, IgnorePaths(ignorePaths)
{
	File = inFile;

	if (inFile)
	{
		// scan local headers
		m_archiveName = filename;
		while (scanHeader && scanLocalHeader());
		
	}
}


CZipReader::~CZipReader()
{
	if (File)
	{		
		File->Close();
		File = 0;
	}
}

//! splits filename from zip file into useful filenames and paths
void
CZipReader::extractFilename(SZipFileEntry* entry)
{
	signed int lorfn = entry->header.FilenameLength; // length of real file name

	if (!lorfn)
		return;

	if (IgnoreCase)
	{
		for (unsigned int i=0; i<entry->zipFileName.length(); ++i)
		{
			char x = entry->zipFileName[i];
			entry->zipFileName[i] = x >= 'A' && x <= 'Z' ? (char) x + 0x20 : (char) x;
		}
	}
	const char* p = entry->zipFileName.c_str() + lorfn;
	
	// suche ein slash oder den anfang.

	while (*p!='/' && p!=entry->zipFileName.c_str())
	{
		--p;
		--lorfn;
	}

	bool thereIsAPath = p != entry->zipFileName.c_str();

	if (thereIsAPath)
	{
		// there is a path
		++p;
		++lorfn;
	}

	entry->simpleFileName = p;
	entry->path = "";

	// pfad auch kopieren
	if (thereIsAPath)
	{
		lorfn = (signed int)(p - entry->zipFileName.c_str());
		
		entry->path = entry->zipFileName.substr( 0, lorfn );

		//entry->path.append(entry->zipFileName, lorfn);
		//entry->path.append ( "" );
	}

	if (!IgnorePaths)
		entry->simpleFileName = entry->zipFileName; // thanks to Pr3t3nd3r for this fix
}

//! scans for a local header, returns false if there is no more local file header.
bool
CZipReader::scanLocalHeader()
{
	char tmp[1024];

	SZipFileEntry entry;
	entry.fileDataPosition = 0;
	memset(&entry.header, 0, sizeof(SZIPFileHeader));

	File->Read(&entry.header, sizeof(SZIPFileHeader));

#if VOX_BIG_ENDIAN
	entry.header.Sig = os::byteswap(entry.header.Sig);
	entry.header.VersionToExtract = os::byteswap(entry.header.VersionToExtract);
	entry.header.GeneralBitFlag = os::byteswap(entry.header.GeneralBitFlag);
	entry.header.CompressionMethod = os::byteswap(entry.header.CompressionMethod);
	entry.header.LastModFileTime = os::byteswap(entry.header.LastModFileTime);
	entry.header.LastModFileDate = os::byteswap(entry.header.LastModFileDate);
	entry.header.DataDescriptor.CRC32 = os::byteswap(entry.header.DataDescriptor.CRC32);
	entry.header.DataDescriptor.CompressedSize = os::byteswap(entry.header.DataDescriptor.CompressedSize);
	entry.header.DataDescriptor.UncompressedSize = os::byteswap(entry.header.DataDescriptor.UncompressedSize);
	entry.header.FilenameLength = os::byteswap(entry.header.FilenameLength);
	entry.header.ExtraFieldLength = os::byteswap(entry.header.ExtraFieldLength);
#endif

	if (entry.header.Sig != 0x04034b50 && entry.header.Sig != ZIP_MAGIC_NUMBER)
	{
		return false; // local file headers end here.
	}

	// read filename
	entry.zipFileName.reserve(entry.header.FilenameLength+2);
	File->Read(tmp, entry.header.FilenameLength);
	tmp[entry.header.FilenameLength] = 0x0;
	entry.zipFileName = tmp;

	extractFilename(&entry);

	// move forward length of extra field.

	if (entry.header.ExtraFieldLength)
		File->Seek(entry.header.ExtraFieldLength, ios::cur);

	// if bit 3 was set, read DataDescriptor, following after the compressed data
	if (entry.header.GeneralBitFlag & ZIP_INFO_IN_DATA_DESCRITOR)
	{
		// read data descriptor
		File->Read(&entry.header.DataDescriptor, sizeof(entry.header.DataDescriptor));
#if VOX_BIG_ENDIAN
		entry.header.DataDescriptor.CRC32 = os::byteswap(entry.header.DataDescriptor.CRC32);
		entry.header.DataDescriptor.CompressedSize = os::byteswap(entry.header.DataDescriptor.CompressedSize);
		entry.header.DataDescriptor.UncompressedSize = os::byteswap(entry.header.DataDescriptor.UncompressedSize);
#endif
	}

	// store position in file
	entry.fileDataPosition = File->Tell();//File->getPos();

	// move forward length of data
	File->Seek(entry.header.DataDescriptor.CompressedSize, ios::cur);

	//#ifdef _DEBUG
	////os::Debuginfo::print("added file from archive", entry.simpleFileName.c_str());
	//#endif

	FileList[GetFileMapKey(entry.simpleFileName)] = entry;
	//FileList.push_back(entry);

	return true;
}

bool CZipReader::getFileInfo(const char* filename, signed int &baseOffset, signed int &fileSize)
{
	ZIP_READER_STRING fname(filename);

	if(IgnorePaths)
		deletePathFromFilename(fname);

	if (IgnoreCase)
	{
		for (unsigned int i = 0; i < fname.length(); ++i)
		{
			char x = fname[i];
			fname[i] = x >= 'A' && x <= 'Z' ? (char) x + 0x20 : (char) x;
		}
	}


	FileListMap::const_iterator it = FileList.find(GetFileMapKey(fname));
	if(it == FileList.end())
		return false;
	
	switch(it->second.header.CompressionMethod)
	{
		case 0: // no compression
		{
			baseOffset = it->second.fileDataPosition;
			fileSize = it->second.header.DataDescriptor.UncompressedSize;
			return true;
		}
		default:
		{
			//os::Printer::log("file has unsupported compression method.", FileList[index].simpleFileName.c_str(), ELL_ERROR);
			return false;
		}
	};
}

// returns count of files in archive
signed int
CZipReader::getFileCount()
{
	return FileList.size();
}

// deletes the path from a filename
void
CZipReader::deletePathFromFilename(ZIP_READER_STRING& filename)
{
	// delete path from filename
	const char* p = filename.c_str() + filename.size();

	// search for path separator or beginning

	while (*p!='/' && *p!='\\' && p!=filename.c_str())
		--p;

	if (p != filename.c_str())
	{
		++p;
		filename = p;
	}
}

int CZipReader::GetFilesInDirectory( const char* dir, glf::core::CZipReader::FileListing& outFiles ) const
{
	FileListMap::const_iterator cur = FileList.begin();
	FileListMap::const_iterator end = FileList.end();
	int foundFileCnt = 0;
	int pathSize = strlen( dir );
	for( ; cur != end ; ++cur)
	{
		const SZipFileEntry& value = (*cur).second;
		const char* zipName = value.zipFileName.c_str();
		if( strstr( zipName, dir  ) == zipName )
		{
			//to avoid returning the folder name since the FileList do have entry only for directory names...
			if( strcmp( zipName, value.path.c_str() ) != 0) 
			{
				// + pathSize to return the filename only, without path.
				outFiles.push_back( value.zipFileName.c_str() + pathSize);
				foundFileCnt++;
			}
		}		
	}
	return foundFileCnt;
}

void CZipReader::ExportHeader(DataStream& outStream)
{
	FileListMap::iterator cur = FileList.begin();
	FileListMap::iterator end = FileList.end();
	int intValue;
	intValue = FileList.size();
	outStream.Write( (void*)&intValue, sizeof(int));
	for( ; cur != end ; ++cur)
	{
		SZipFileEntry&				value = (*cur).second;
		intValue = strlen( value.zipFileName.c_str() );
		outStream.Write( (void*)&intValue, sizeof( int ));
		outStream.Write( (void*)value.zipFileName.c_str(), intValue * sizeof(char));
		outStream.Write( (void*)&value.fileDataPosition, sizeof(int));
		outStream.Write( (void*)&value.header, sizeof(SZIPFileHeader));
	}
}

void CZipReader::ImportHeader(DataStream& inStream)
{
	static char buffer[MAX_ZIP_FILENAME];	//max filename size...

	int numEntries;
	int strSize;
	SZipFileEntry entry;
	inStream.Read( &numEntries, sizeof(int));
	for(int i = 0; i < numEntries; i++)
	{
		inStream.Read( &strSize, sizeof(int));	
		if( strSize < MAX_ZIP_FILENAME )
		{
			inStream.Read( buffer, strSize );
			buffer[strSize] = '\0';
			entry.zipFileName = buffer;
			inStream.Read( &entry.fileDataPosition, sizeof(int));
			inStream.Read( &entry.header, sizeof(SZIPFileHeader));
			
			extractFilename(&entry);
			FileList[GetFileMapKey(entry.simpleFileName)] = entry;
		}
		else
		{
			//a file with path of more then 1024 was in the header file, this is not supported...
			break;
		}
	}
}

}	//core
}	//glf
